﻿using System;
using System.Web;
using System.Security.Principal;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.Administration;
using System.DirectoryServices.AccountManagement;
using System.IO;

namespace SiteSecurity
{
    public partial class SecurityHierarchy : LayoutsPageBase
    {
        // Change this value to the domain that Active Directory is tied to
        private const String domain = "ce";

        // String Constants
        private const String broken = " (broken)"; // This is used when the treeview is parsed and specially formatted.
        private const String security = "<u>Security</u>"; // ^ Same as this ^
        private const String siteCollectionText = " (site collection)";
        private const String siteText = " (site)";
        private const String listText = " (list/library)";
        private const String folderText = " (folder)";
        private const String errorWeb = "<i>Error Loading Subsite</i>";
        private const String errorList = "<i>Error Loading List/Library</i>";
        private const String errorFolder = "<i>Error Loading Folder</i>";
        private const int GroupLimit = 100;

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                loadPage();
            }
        }

        // Load Page Contents
        protected void loadPage()
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(SPContext.Current.Site.MakeFullUrl(SPContext.Current.Web.ServerRelativeUrl)))
                {
                    initLabels();

                    securityAdministrationViewer.Nodes.Clear();
                    site.CatchAccessDeniedException = false;
                    securityAdministrationViewer.Nodes.Add(addSite(site));
                    securityAdministrationViewer.ExpandDepth = 1;

                    foreach (TreeNode node in securityAdministrationViewer.Nodes)
                    {
                        parseTree(node);
                    }

                    ADEntries.Nodes.Clear();
                    ADEntries.Nodes.Add(ADList(site));
                    ADEntries.ExpandDepth = 1;
                }
            });
        }

        // Label text
        private void initLabels()
        {
            brokenInheritanceLabel.InnerHtml = "Outlines where special security (inheritance breaking) has been defined from the current site collection.<br /><br />" +
                "<b>Bold</b> - Security Inheritance is broken.<br />" +
                "<u>Security</u> - All security defined for this scope.<br /><br />";

            ADEntriesLabel.InnerHtml = "All Active Directory groups and their contained members.<br /><br />" +
                "Note: This will only display the first " + GroupLimit + " members in each group.<br />";
        }

        // Event for Expand button
        protected void expand_Click(object sender, EventArgs e)
        {
            securityAdministrationViewer.ExpandAll();
        }

        // Event for Collapse button
        protected void collapse_Click(object sender, EventArgs e)
        {
            securityAdministrationViewer.CollapseAll();
        }

        // Incomplete Save-To-Excel function. Not used.
        /*protected void save_Click(object sender, EventArgs e)
        {
            string URL = "C:/security.csv";
            FileInfo fileInfo = new FileInfo(URL);

            if (fileInfo.Exists)
            {
                Response.Clear();
                Response.AddHeader("Content-Disposition", "attachment; filename=" + fileInfo.Name);
                Response.AddHeader("Content-Length", fileInfo.Length.ToString());
                Response.ContentType = "application/octet-stream";
                Response.Flush();
                Response.WriteFile(fileInfo.FullName);
            }
        }*/

        #region SharePoint Broken Inheritance
        /****
         * The next several functions recursively parse the site collection to build the tree(s).
         * returnSecurity: Return the Security for a given scope
         * addSite: The root node (site collection).
         * addWebs: Add subsites node, any number of sites deep.
         * addList: Add a list or library node.
         * addFolders: Add folder node, any number of folders deep.
         * parseTree: Go back through tree and format certain areas.
        ****/

        TreeNode returnSecurity(Object scope, SPSite RootSite, TreeNode securityNodes)
        {
            securityNodes.ChildNodes.Clear();
            SPRoleAssignmentCollection rac = null;
            String nodeString;
            int commaCount;

            // Determine if the object is a site, web, list/library, or folder. Convert accordingly.
            if (scope.GetType().ToString() == "Microsoft.SharePoint.SPSite") // Site Collection
            {
                SPSite site = (SPSite)scope;
                rac = site.RootWeb.RoleAssignments;
                site.Dispose();
            }
            else if (scope.GetType().ToString() == "Microsoft.SharePoint.SPWeb") // Sub Site
            {
                // We were having propblems  with "rac" so we assigned a value through Elevated Privileges
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPSite tempSite = new SPSite(RootSite.ID))
                    {
                        using (SPWeb tempWeb = tempSite.OpenWeb(((SPWeb)scope).ID))
                        {
                            rac = tempWeb.RoleAssignments;
                        }
                    }
                });
            }
            else if (scope.GetType().ToString() == "Microsoft.SharePoint.SPList") // List
            {
                SPList list = (SPList)scope;
                rac = list.RoleAssignments;
            }
            else if (scope.GetType().ToString() == "Microsoft.SharePoint.SPDocumentLibrary") // Library
            {
                SPDocumentLibrary dl = (SPDocumentLibrary)scope;
                rac = dl.RoleAssignments;
            }
            else if (scope.GetType().ToString() == "Microsoft.SharePoint.SPFolder") // Folder
            {
                SPFolder folder = (SPFolder)scope;
                rac = folder.Item.RoleAssignments;
            }
            else return new TreeNode(scope.GetType().ToString());

            // Parse security and output members
            foreach (SPRoleAssignment ra in rac)
            {
                // If the member is a SharePoint Group
                if (ra.Member.GetType().ToString() == "Microsoft.SharePoint.SPGroup")
                {
                    // Return Permission Levels
                    nodeString = RootSite.RootWeb.SiteGroups.GetByID(ra.Member.ID).ToString() + " - <i><font size=1>";
                    commaCount = 0;
                    foreach (SPRole r in ra.Member.Roles) // SPRole is deprecated, but an alternative solution was not found
                    {
                        if (commaCount == 0)
                            nodeString += r.Name;
                        else
                            nodeString += ", " + r.Name;
                        commaCount++;
                    }
                    nodeString += "</font><i>";
                    TreeNode groupMembers = new TreeNode(nodeString);
                    groupMembers.SelectAction = TreeNodeSelectAction.None;
                    securityNodes.ChildNodes.Add(groupMembers);

                    // Elevated privileges are needed here, so a new Site and Web need to be declared in the scope.
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        using (SPSite tempSite = new SPSite(RootSite.ID))
                        {
                            using (SPWeb tempWeb = tempSite.RootWeb)
                            {
                                // Return users in SharePoint group
                                foreach (SPUser user in tempWeb.SiteGroups.GetByID(ra.Member.ID).Users)
                                {
                                    TreeNode child = new TreeNode(user.Name);
                                    child.SelectAction = TreeNodeSelectAction.None;
                                    if(!child.Text.Contains("System Account"))
                                        groupMembers.ChildNodes.Add(child);
                                }
                            }
                        }
                    });
                }
                // If not a SharePoint Group (Active Directory ID)
                else
                {
                    // Return Permission Levels
                    nodeString = ra.Member.Name + " - <i><font size=1>";
                    commaCount = 0;
                    foreach (SPRole r in ra.Member.Roles) // SPRole is deprecated, but an alternative solution was not found
                    {
                        if (commaCount == 0)
                            nodeString += r.Name;
                        else
                            nodeString += ", " + r.Name;
                        commaCount++;
                    }
                    nodeString += "</font></i>";
                    TreeNode permission = new TreeNode(nodeString);
                    permission.SelectAction = TreeNodeSelectAction.None;
                    if (!permission.Text.Contains("System Account"))
                        securityNodes.ChildNodes.Add(permission);
                }
            }

            return securityNodes;
        }

        TreeNode addSite(SPSite site)
        {
            TreeNode node, listNodes, subNode, securityNode;
            node = new TreeNode(site.Url + siteCollectionText);
            node.NavigateUrl = site.Url;
            listNodes = new TreeNode("Home" + siteText + broken);
            listNodes.NavigateUrl = site.Url;
            securityNode = new TreeNode(security);

            securityNode.ChildNodes.Add(new TreeNode("..."));
            listNodes.ChildNodes.Add(securityNode);

            // Recursively parse lists at root
            foreach (SPList list in site.RootWeb.Lists)
            {
                // Make sure list is visibly accessible and not Site Collection Images or Style Library
                if (!list.Hidden && !list.Title.Contains("Site Collection Images") && !list.Title.Contains("Style Library"))
                {
                    subNode = addList(list);
                    if (list.HasUniqueRoleAssignments || subNode.ChildNodes.Count > 0)
                    {
                        listNodes.ChildNodes.Add(subNode);
                    }
                }
            }

            node.ChildNodes.Add(listNodes);

            // Recursively parse root sub sites
            foreach (SPWeb web in site.RootWeb.Webs)
            {
                subNode = addWebs(web);
                if (web.HasUniqueRoleAssignments || subNode.ChildNodes.Count > 0)
                {
                    node.ChildNodes.Add(subNode);
                }
                web.Dispose();
            }
            return node;
        }

        TreeNode addWebs(SPWeb web)
        {
            TreeNode node, subNode, securityNode;
            subNode = new TreeNode();
            securityNode = new TreeNode(security);

            try
            {
                if (web.HasUniqueRoleAssignments)
                {
                    node = new TreeNode(web.Title + siteText + broken);
                    node.NavigateUrl = web.Url;
                    securityNode.ChildNodes.Add(new TreeNode("..."));
                    node.ChildNodes.Add(securityNode);
                }
                else
                {
                    node = new TreeNode(web.Title + siteText);
                    node.NavigateUrl = web.Url;
                }

                // Recursively parse all lists on current site
                foreach (SPList list in web.Lists)
                {
                    if (!list.Hidden)
                    {
                        subNode = addList(list);
                        if (list.HasUniqueRoleAssignments || subNode.ChildNodes.Count > 0)
                        {
                            node.ChildNodes.Add(subNode);
                        }
                    }
                }

                // Recursively parse sub sites of the currect site
                foreach (SPWeb childWeb in web.Webs)
                {
                    subNode = addWebs(childWeb);
                    if (childWeb.HasUniqueRoleAssignments || subNode.ChildNodes.Count > 0)
                    {
                        node.ChildNodes.Add(subNode);
                    }
                    childWeb.Dispose();
                }
                return node;
            }
            catch (Exception ex)
            {
                node = new TreeNode(errorWeb);
                String message = "Security Page - Error Loading Web For " + SPContext.Current.Site.Url + " - " + ex.Message;

                SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
                diagSvc.WriteTrace(0, new SPDiagnosticsCategory("Load Page", TraceSeverity.Medium, EventSeverity.Warning), TraceSeverity.Medium, message, ex.StackTrace);

                return node;
            }
        }

        TreeNode addList(SPList list)
        {
            TreeNode node, subNode, securityNode;
            subNode = new TreeNode();
            securityNode = new TreeNode(security);

            try
            {
                if (list.HasUniqueRoleAssignments)
                {
                    node = new TreeNode(list.Title + listText + broken);
                    node.NavigateUrl = list.DefaultViewUrl;
                    returnSecurity(list, SPContext.Current.Site, securityNode);
                    node.ChildNodes.Add(securityNode);
                }
                else
                {
                    node = new TreeNode(list.Title + listText);
                    node.NavigateUrl = list.DefaultViewUrl;
                }

                // Recursively parse root folders on list/library
                foreach (SPFolder folder in list.RootFolder.SubFolders)
                {
                    if (folder.Item != null) // Some hidden folders exist and need to be excluded
                    {
                        subNode = addFolders(folder);
                        if (folder.Item.HasUniqueRoleAssignments || subNode.ChildNodes.Count > 0)
                        {
                            node.ChildNodes.Add(subNode);
                        }
                    }
                }
                return node;
            }
            catch (Exception ex)
            {
                node = new TreeNode(errorList);
                String message = "Security Page - Error Loading List For " + SPContext.Current.Site.Url + " - " + ex.Message;

                SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
                diagSvc.WriteTrace(0, new SPDiagnosticsCategory("Load Page", TraceSeverity.Medium, EventSeverity.Warning), TraceSeverity.Medium, message, ex.StackTrace);

                return node;
            }
        }

        TreeNode addFolders(SPFolder folder)
        {
            TreeNode node, subNode, securityNode;
            subNode = new TreeNode();
            securityNode = new TreeNode(security);

            try
            {
                if (folder.Item.HasUniqueRoleAssignments)
                {
                    node = new TreeNode(folder.Name + folderText + broken);
                    node.NavigateUrl = folder.ServerRelativeUrl;
                    returnSecurity(folder, SPContext.Current.Site, securityNode);
                    node.ChildNodes.Add(securityNode);
                }
                else
                {
                    node = new TreeNode(folder.Name + folderText);
                    node.NavigateUrl = folder.ServerRelativeUrl;
                }

                // Recursively parse sub folders of the current folder
                foreach (SPFolder childFolder in folder.SubFolders)
                {
                    if (childFolder.Item != null) // Some hidden folders exist and need to be excluded
                    {
                        subNode = addFolders(childFolder);
                        if (childFolder.Item.HasUniqueRoleAssignments || subNode.ChildNodes.Count > 0)
                        {
                            node.ChildNodes.Add(subNode);
                        }
                    }
                }
                return node;
            }
            catch (Exception ex)
            {
                node = new TreeNode(errorFolder);
                String message = "Security Page - Error Loading Folder For " + SPContext.Current.Site.Url + " - " + ex.Message;

                SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
                diagSvc.WriteTrace(0, new SPDiagnosticsCategory("Load Page", TraceSeverity.Medium, EventSeverity.Warning), TraceSeverity.Medium, message, ex.StackTrace);

                return node;
            }
        }

        /******
         * Parses tree and replaces text in leafs with special formatting.
         * This is a method of tagging properties onto a leaf so that text
         * can later be formatted.
         * Broken Inheritance leaf = bold
         * Security leaf = italicized
        ******/
        void parseTree(TreeNode node)
        {
            // Make bold where inheritance is broken
            if (node.Text.Contains(broken))
            {
                node.Text = node.Text.Replace(broken, "");
                node.Text = "<b>" + node.Text + "</b>";
            }

            // Expand all nodes except for "Security" ones and their children
            if (!node.Text.Equals(security))
            {
                node.Expand();
                if (node.Parent != null)
                    if (node.Parent.Text.Equals(security))
                        node.Collapse();
            }

            // Parse through the tree recursively
            foreach (TreeNode childNode in node.ChildNodes)
            {
                parseTree(childNode);
            }
        }

        // Dynamically fetch security so it is not all need on initial page load. Only works for site collection and subsites.
        protected void SP_Node_Expand(Object sender, TreeNodeEventArgs e)
        {
            if (e.Node.Text.Contains(security))
            {
                if (e.Node.Parent.Text.Contains(siteCollectionText))
                    returnSecurity(new SPSite(e.Node.Parent.NavigateUrl), SPContext.Current.Site, e.Node);
                else if (e.Node.Parent.Text.Contains(siteText))
                    using(SPSite site = new SPSite(e.Node.Parent.NavigateUrl))
                        using(SPWeb web = site.OpenWeb())
                            returnSecurity(web, SPContext.Current.Site, e.Node);
                else if (e.Node.Parent.Text.Contains(listText)) { } // Not developed
                else if (e.Node.Parent.Text.Contains(folderText)) { } // Not developed
            }
        }

        #endregion

        #region Active Directory Groups
        /****
         * The next several functions recursively parse Active Directory Users and Groups assigned on the site collection.
         * ADList: Root list of all AD ID's assigned on site.
         * returnGroupMembers: Recursively drill down into AD Groups. Limited by fixed scope and size.
        ****/

        private TreeNode ADList(SPSite site)
        {
            TreeNode node, memberNode;

            node = new TreeNode(site.Url);

            foreach (SPUser user in site.RootWeb.SiteUsers)
            {
                if (user.IsDomainGroup && !user.ToString().Contains("NT AUTHORITY"))
                {
                    memberNode = new TreeNode(user.Name);
                    memberNode.SelectAction = TreeNodeSelectAction.None;
                    memberNode.ChildNodes.Add(new TreeNode("...")); // This is done only to cause the node to have an Expand button
                    if (memberNode != null)
                        node.ChildNodes.Add(memberNode);
                }
            }
            return node;
        }

        private void returnGroupMembers(TreeNode AD)
        {
            TreeNode memberNode = new TreeNode();
            GroupPrincipal p,j;
            String name = "";
            PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain);

            ctx = new PrincipalContext(ContextType.Domain, domain);

            WindowsIdentity temp = WindowsIdentity.GetCurrent();

            AD.SelectAction = TreeNodeSelectAction.None;

            // Clear all child nodes to repopulate
            if (AD.Text != SPContext.Current.Site.Url)
                AD.ChildNodes.Clear();
            else
                return;

            try
            {
                p = GroupPrincipal.FindByIdentity(ctx, AD.Text);
                if (p != null) // If p is a Group and not an Individual
                {
                    int NumOfUsers = 0;
                    foreach (Principal member in p.GetMembers())
                    {
                        // Only pull a certain amount of members for each group
                        if (NumOfUsers < GroupLimit) NumOfUsers++;
                        else
                        {
                            AD.ChildNodes.Add(new TreeNode("..."));
                            break;
                        }

                        // If no Display Name is found, use Name
                        if (member.DisplayName != null) name = member.DisplayName;
                        else name = member.Name;

                        // Make new member
                        memberNode = new TreeNode(name);
                        memberNode.SelectAction = TreeNodeSelectAction.None;

                        // If group, add "...". This is done only to cause the node to have an Expand button
                        if(member.StructuralObjectClass == "group")
                            memberNode.ChildNodes.Add(new TreeNode("..."));
                        AD.ChildNodes.Add(memberNode);
                    }
                }
            }
            catch (Exception ex)
            {
                //node.ChildNodes.Add(new TreeNode(ex.HelpLink));
                //node.ChildNodes.Add(new TreeNode(ex.Message));
                //node.ChildNodes.Add(new TreeNode(ex.Source));
                //node.ChildNodes.Add(new TreeNode(ex.ToString()));
            }
        }

        protected void AD_Node_Expand(Object sender, TreeNodeEventArgs e)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                returnGroupMembers(e.Node);
            });
        }

        #endregion
    }
}
